(*****************************************************************************
 * Delphi Plug-in Framework (DPF)
 * Copyright (C) 2006-2007 Tao Yuan
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the
 * License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.                                                               }
 *
 * The Initial Developer of the Original Code is Tao Yuan. Portions created by
 * Tao Yuan are Copyright (C) Tao Yuan (taoy.cn att gmail dott com)
 * All rights reserved.
 *****************************************************************************)
 
unit RDVersion;

interface

uses
  Rcl,
  Classes, SysUtils;

const
  (**
   * Version identifier parts separator.
   *)
  VERSEPARATOR = '.';

type
  (**
   * This class represents a plug-in version identifier.
   * <br>
   * @version $Id: Version.java,v 1.4 2005/02/02 18:27:25 ddimon Exp $
   *)

  TVersion = class(TObject)
  private
    FBuild: Integer;
    FMajor: Integer;
    FMinor: Integer;
    FName: string;
    FVersionString: string;
  protected
    function compareObjects(object2: TObject): integer;
  public
    constructor Create(aMajor, aMinor, aBuild: Integer; aName: string);
    destructor Destroy; override;
    function compareTo(obj: TObject): Integer;
    function equals(obj: TObject): Boolean;
    function hashCode: Integer;
    function isCompatibleWith(other: TVersion): Boolean;
    function isEquivalentTo(other: TVersion): Boolean;
    function isGreaterOrEqualTo(other: TVersion): Boolean;
    function isGreaterThan(other: TVersion): Boolean;
    class function parse(str: string): TVersion;
    function toString: String;
    property Build: Integer read FBuild;
    property Major: Integer read FMajor;
    property Minor: Integer read FMinor;
    property Name: string read FName;
  end;

implementation

constructor TVersion.Create(aMajor, aMinor, aBuild: Integer; aName: string);
begin
  Self.FMajor := aMajor;
  Self.FMinor := aMinor;
  Self.FBuild := aBuild;
  Self.FName := aName;

  FVersionString := '';
end;

destructor TVersion.Destroy;
begin
  inherited;
  // TODO -cMM: TVersion.Destroy default body inserted
end;

(* **************** tString ****************** *)

function TVersion.compareObjects(object2: TObject): integer;
begin
//     throwComparableException(object2, TVersion);

     result := compareTo(object2);
end;


(**
 * @see java.lang.Comparable#compareTo(java.lang.Object)
 *)
function TVersion.compareTo(obj: TObject): Integer;
var
  other: TVersion;
begin
  if (equals(obj)) then
      Result := 0
  else if (not(obj is TVersion)) then
    raise Exception.Create('Class can not cast!')
  else
  begin
    other := TVersion(obj);
    if (major <> other.major) then
        Result := major - other.major
    else if (minor <> other.minor) then
        Result := minor - other.minor
    else if (build <> other.build) then
        Result := build - other.build
    else
      Result := CompareText(name, other.name);
  end;
end;


(**
 * @see java.lang.Object#equals(java.lang.Object)
 *)
function TVersion.equals(obj: TObject): Boolean;
var
  other: TVersion;
begin
  if (Self = obj) then
    Result := True
  else if not(obj is TVersion) then
    Result := False
  else
  begin
    other := TVersion(obj);
    if ((major <> other.major) or (minor <> other.minor)
            or (build <> other.build)
            or (CompareText(Name, other.name) <> 0)) then
      Result := False
    else Result := True
  end;  
end;

(**
 * @see java.lang.Object#hashCode()
 *)
function TVersion.hashCode: Integer;
begin
//  Result := toString.hashCode;
end;

(**
 * Compares two version identifiers for compatibility.
 * <p>
 * A version identifier is considered to be compatible if its major
 * component equals to the argument major component, and its minor component
 * is greater than or equal to the argument minor component.
 * If the minor components are equal, than the build component of the
 * version identifier must be greater than or equal to the build component
 * of the argument identifier.
 * </p>
 *
 * @param other the other version identifier
 * @return <code>true</code> if this version identifier
 *         is compatible with the given version identifier, and
 *         <code>false</code> otherwise
 *)
function TVersion.isCompatibleWith(other: TVersion): Boolean;
begin
  if (other = nil) then
    Result := False
  else if (major <> other.major) then
    Result := False
  else if (minor > other.minor) then
    Result := True
  else if (minor < other.minor) then
    Result := False
  else if (build >= other.build) then
    Result := True
  else Result := False;
end;

(**
 * Compares two version identifiers for equivalency.
 * <p>
 * Two version identifiers are considered to be equivalent if their major
 * and minor components equal and are at least at the same build level
 * as the argument.
 * </p>
 *
 * @param other the other version identifier
 * @return <code>true</code> if this version identifier
 *         is equivalent to the given version identifier, and
 *         <code>false</code> otherwise
 *)
function TVersion.isEquivalentTo(other: TVersion): Boolean;
begin
  if (other = nil) then
    Result := False
  else if (major <> other.major) then
    Result := False
  else if (minor <> other.minor) then
    Result := False
  else if (build >= other.build) then
    Result := True
  else Result := False;
end;

(**
 * Compares two version identifiers to see if this one is
 * greater than or equal to the argument.
 * <p>
 * A version identifier is considered to be greater than or equal
 * if its major component is greater than the argument major
 * component, or the major components are equal and its minor component
 * is greater than the argument minor component, or the
 * major and minor components are equal and its build component is
 * greater than the argument build component, or all components are equal.
 * </p>
 *
 * @param other the other version identifier
 * @return <code>true</code> if this version identifier
 *         is compatible with the given version identifier, and
 *         <code>false</code> otherwise
 *)
function TVersion.isGreaterOrEqualTo(other: TVersion): Boolean;
begin
  if other = nil then
    Result := False
  else if (major > other.major) then
    Result := True
  else if ((major = other.major) and (minor > other.minor)) then
    Result := True
  else if ((major = other.major) and (minor = other.minor)
          and (build > other.build)) then
    Result := True
  else if ((major = other.major) and (minor = other.minor)
          and (build = other.build)
          and (CompareText(name, other.name) = 0) ) then
    Result := True
  else Result := False;   
end;

(**
 * Compares two version identifiers for order using multi-decimal
 * comparison.
 *
 * @param other the other version identifier
 * @return <code>true</code> if this version identifier
 *         is greater than the given version identifier, and
 *         <code>false</code> otherwise
 *)
function TVersion.isGreaterThan(other: TVersion): Boolean;
begin
  if (other = nil) then
      Result := False
  else if (major > other.major) then
      Result := True
  else if (major < other.major) then
      Result := False
  else if (minor > other.minor) then
      Result := True
  else if (minor < other.minor) then
      Result := False
  else if (build > other.build) then
      Result := True
  else Result := False;
end;

(**
 * Parses given string as version identifier. All missing parts will be
 * initialized to 0 or empty string. Parsing starts from left side of the
 * string.
 * @param str version identifier as string
 * @return version identifier object
 *)
class function TVersion.parse(str: string): TVersion;
var
  st: TStringList;
  major,
  minor,
  build: Integer;
  name: string;
begin
  major := 0;
  minor := 0;
  build := 0;
  Name := '';
  //Todo : Use dynamic array replace TStrings
  st := TStringList.Create;
  try
    ExtractStrings([VERSEPARATOR], [], PChar(str), st);

    if st.Count > 0 then
      major := StrToInt(st.Strings[0]);

    if st.Count > 1 then
      minor := StrToInt(st.Strings[1]);

    if st.Count > 2 then
      build := StrToInt(st.Strings[2]);

    if st.Count > 3 then
      name := st.Strings[3];
  finally
    st.Free;
  end;

  Result := TVersion.Create(major, minor, build, name);
end;

(**
 * Returns the string representation of this version identifier.
 * The result satisfies
 * <code>version.equals(new Version(version.toString()))</code>.
 * @return the string representation of this version identifier
 *)
function TVersion.toString: String;
const
  vers = '%d'+ VERSEPARATOR + '%d'+ VERSEPARATOR + '%d';
begin
  if FVersionString = '' then
  begin
    FVersionString := Format(vers, [major, minor, build]);
    if name <> '' then
      FVersionString := FVersionString + VERSEPARATOR + name;
  end;
  Result := FVersionString;
end;

end.
